/* -*-  Mode:C; c-basic-offset:8; tab-width:8; indent-tabs-mode:t -*- */
/*
 * Copyright (c) 1999, 2000 Massachusetts Institute of Technology.
 *
 * This software is being provided by the copyright holders under the GNU
 * General Public License, either version 2 or, at your discretion, any later
 * version. (See the file `LICENSE' in the source distribution.)
 *
 * This software is provided "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. The entire risk as
 * to the quality and performance of the program is with you. See the GNU
 * General Public License for more details.
 *
 * The name and trademarks of copyright holders may NOT be used in 
 * advertising or publicity pertaining to the software without specific, 
 * written prior permission. Title to copyright in this software and any 
 * associated documentation will at all times remain with copyright 
 * holders. See the file CMAUTHORS which should have accompanied this software 
 * for a list of all copyright holders. 
 * 
 * Particular files in this distribution may be derived from previously
 * copyrighted software. This copyright applies only to those changes made by
 * the copyright holders listed in the CMAUTHORS file.
 */
/*
 * An implementation of the Congestion Manager (CM) for Linux.
 * This file does CM flow management.
 */
#include <linux/config.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/malloc.h>
#include <linux/skbuff.h>
#include <net/sock.h>
#include <linux/inet.h>
#include <net/ip.h>
#include <net/cm.h>
#include <net/cm_rrsched.h>
#ifdef CONFIG_CM_BINOMIAL
#include <net/cm_bincongctl.h>
#else
#include <net/cm_tcpcongctl.h>
#endif
#include <net/cm_debug.h>

extern cm_t cm;

/*
 * Make a new macroflow and add this flow to it.
 */
int cm_make_mflow(cm_flow_t *flow)
{
	cm_macroflow_t *mf, *mflow;
	__u32 dst = flow->sk->daddr;

	CM_DEBUG_ENTER();

	/* 
	 * Make new macroflow here, making new scheduler and congctl.
	 */
	if ((mf = (cm_macroflow_t *)kmalloc(sizeof(cm_macroflow_t),GFP_KERNEL))
	    == NULL)
		return -1;

        mf->id = (int) mf;
	mf->xmittime = 0;	/* XXX */
	mf->flows = flow;
	mf->nflows = 1;
	mf->mtu = flow->sk->dst_cache->pmtu;
	mf->key.dst = flow->sk->daddr;
	mf->flows_lock = RW_LOCK_UNLOCKED;
	if ((mf->sched = cms_init(mf)) == NULL) {
		kfree(mf);
		return -1;
	}
	/* 
	 * For now always use rrsched... 
	 * XXX should make calls to change this later.
	 */
	cmrrs_init(mf->sched);
	if ((mf->congctl = cmcc_init(mf)) == NULL) {
		printk("make_mflow: unable to malloc, returning -1\n");
		kfree(mf);
		return -1;
	}
	/* 
	 * For now always use tcpcc... 
	 * XXX should make calls to change this later.
	 */
	/* 
	 * XXX Default to binomial if compiled with binomial option set.
	 */
#ifdef CONFIG_CM_BINOMIAL
	printk("calling binomial\n");
	cmbincc_init(mf->congctl);
#else
	cmtcpcc_init(mf->congctl);
#endif
  
	mf->prev = NULL;
	mf->next = NULL;
	/*
	 * Stick new macroflow in the hash table at the right place, 
	 * chaining if necessary.
	 */
	write_lock_bh(&cm.mflowtab_lock); 
	if ((mflow = cm.mflowtab[dst%CM_TABLESIZE]) != NULL) {
		/* chain 'em up */
		mf->next=mflow;
	}
	cm.mflowtab[dst%CM_TABLESIZE] = mf;
	write_unlock_bh(&cm.mflowtab_lock); 

	flow->mflow = mf;
	return (int)mf;		/* unique macroflow ID */
}

/*
 * Get rid of a macroflow; clean-up called when last flow disappears on mf.
 */
void cm_delete_mflow(cm_macroflow_t *mf)
{

        CM_DEBUG_ENTER();
	write_lock_bh(&cm.mflowtab_lock); 
	cms_free(mf);
	cmcc_free(mf);
	if (mf->next && mf->prev)
		cm.mflowtab[mf->key.dst%CM_TABLESIZE] = NULL;
	if (mf == cm.mflowtab[mf->key.dst%CM_TABLESIZE])
		cm.mflowtab[mf->key.dst%CM_TABLESIZE] = mf->next;
	if (mf->prev)
		mf->prev->next = mf->next;
	cm.num_mflows--;
	write_unlock_bh(&cm.mflowtab_lock);
	if (mf->cm_restart_timer.prev)
		del_timer(&mf->cm_restart_timer);
	kfree(mf);
}

/*
 * Does this macroflow exist?  Return 0 if it does, -1 if not.
 * XXX why this weird semantics of the return value???
 */
int cm_mflow_exists(cm_macroflow_t *mf)
{
	cm_macroflow_t *cur;
	int i;

	read_lock_bh(&cm.mflowtab_lock); 

	for (i = 0 ; i < CM_TABLESIZE ; i++) {
		for (cur = cm.mflowtab[i]; cur != NULL; cur = cur->next) {
			if (cur == mf) {
				read_unlock_bh(&cm.mflowtab_lock); 
				return 0;
			}
		}
	}
	read_unlock_bh(&cm.mflowtab_lock);  
	return -1;
}

/*
 * Classifies sock *sk to a macroflow based on destination and port fields.
 * Use only dst to hash; chain the others.
 */
cm_macroflow_t *cm_classify(struct sock *sk)
{
	__u32 dst = sk->daddr;
	/* __u16 dport = sk->dport; */
	cm_macroflow_t *mflow;

	CM_DEBUG_ENTER();

	read_lock_bh(&cm.mflowtab_lock);
	for (mflow = cm.mflowtab[dst%CM_TABLESIZE]; mflow != NULL; 
	     mflow = mflow->next) {
		/* for now just destination */
	        if (mflow->key.dst == dst) {
			read_unlock_bh(&cm.mflowtab_lock);
		        return mflow;
		}
	}

	read_unlock_bh(&cm.mflowtab_lock);  
	return NULL;
}

/*
 * When state has already been set up, no need to use classify, just 
 * use the back pointers.  I'm not sure we ever need this, but it's there...
 */
inline cm_macroflow_t *cm_find_mflow(struct sock *sk)
{
	if (SK_CM_FLOW(sk) == NULL)
		return NULL;
	return SK_CM_MFLOW(sk);
}


/* 
 * Given a data socket, find the corresponding control socket.
 */
struct sock *cm_find_sk(struct sock *sk)
{
	__u32 dst;
	__u16 dport;
	cm_macroflow_t *mflow;
	cm_flow_t *flow;

	CM_DEBUG_ENTER();

	if (sk==NULL)
		return NULL;
	dst = sk->daddr;
	dport = sk->dport;
	read_lock_bh(&cm.mflowtab_lock);  
	mflow = cm.mflowtab[dst%CM_TABLESIZE];
	read_unlock_bh(&cm.mflowtab_lock); 
	if (mflow==NULL) { printk("mflow null\n"); return NULL; }
	read_lock_bh(&mflow->flows_lock); 
	for (flow = mflow->flows; flow != NULL; flow = flow->next) {
		if (flow->sk == NULL) {
			printk("flow %p has no sk\n", flow);
			continue;
		}
		if (flow->sk->daddr == dst && flow->sk->dport == dport) {
			read_unlock_bh(&mflow->flows_lock); 
			return flow->sk;
		}
	}
	read_unlock_bh(&mflow->flows_lock); 
	printk("Control socket not found for %d %d\n",dst,dport);
	return NULL;
}

/*
 * Add a flow to a macroflow.  Neither flow nor mflow can be NULL coming here.
 * Return current number of active flows on the macroflow.
 */
int cm_add_flow(cm_macroflow_t *mflow, cm_flow_t *flow)
{
	int rc;

        CM_DEBUG_ENTER();
	write_lock_bh(&mflow->flows_lock); 
	flow->mflow = mflow;
	flow->next = mflow->flows;
	mflow->flows = flow;
	rc = ++mflow->nflows;
	write_unlock_bh(&mflow->flows_lock);
	return rc;
}

/*
 * Delete a flow from a macroflow.  If macroflow then has no more flows,
 * delete it.
 */
int cm_del_flow(cm_macroflow_t *mflow, cm_flow_t *flow)
{
	cm_flow_t *prev = NULL;
	cm_flow_t *ith;
	int rc;

	CM_DPRINTF(CM_DEBUG_FUNCALL,
		   "cm_debug:  entering %s  mflow %p flow %p\n",
		   __FUNCTION__, mflow, flow);

	cms_delete_flow(mflow->sched, flow);

	write_lock_bh(&mflow->flows_lock);
	ith = mflow->flows;
	if (ith == flow) {
		mflow->flows = flow->next;
	} else {
		while (1) {
			prev = ith;
			ith = ith->next;
			if (ith == NULL) {
				printk("cm_del_flow: BUG flow not in mflow list\n");
				write_unlock_bh(&mflow->flows_lock); 
				return mflow->nflows;
			}
			if (ith == flow) break;
		}
		prev->next = ith->next;
	}

	
	if ((rc = --mflow->nflows) == 0) {
		cm_delete_mflow(mflow);
	}
	write_unlock_bh(&mflow->flows_lock);
	return rc;
}

#ifdef CONFIG_CM_BINOMIAL
cm_mflow_setcongalg(cm_macroflow_t *mf, int type)
{
	if (type == BINOMIAL) {
		printk("calling binomial\n");
		cmbincc_init(mf->congctl);
	} 
	if (type == AIMD) {
		printk("calling aimd\n");
		cmtcpcc_init(mf->congctl);
	}
}
#endif
